#include <stdio.h>
#include <stdlib.h>
#include "file.h"
#include <string.h>

#define REG_OCUPADO 0
#define REG_LIBRE 1

unsigned posicionar(unsigned long nrec, unsigned long maxReg, unsigned tamReg);
int validarPosicion(unsigned long nrec, unsigned long maxReg);

unsigned long divisionTecho(unsigned long dividendo, int divisor) {
	if (dividendo % divisor == 0 ) return dividendo / divisor;

	return (dividendo / divisor) + 1;
}

int R_BUSQBINARIA(fHandler *handler, const char *nombre) {
	int centro, inf, sup;
	int cmp;
	regPer aux;

	inf = 0;
	sup = handler->maxReg - 1;

	while ( inf <= sup) {
		centro = ((sup - inf) / 2) + inf;
		R_READ(handler, centro, &aux);
		cmp = strcmp(nombre, aux.nombre);
		if ( !cmp ) return centro;

		if ( cmp < 0)
			sup = centro - 1;
		else
			inf = centro + 1;
	}
	return -1;

}


int R_CREATE (const char* nombre_fisico, int tam_registro, unsigned long max_reg) {
	FILE *fileAux;
	unsigned i;
	void *regAux;
	char *libre;

	if ((tam_registro < 1) || (max_reg < 1)) return RES_ERROR;
	if (fopen(nombre_fisico,"rb")) return RES_EXISTE;

	fileAux = fopen(nombre_fisico, "wb");

	if (!fileAux) return RES_ERROR;

	//Inicio: Datos del header del archivo para la organizacion
	if (fwrite(&tam_registro, sizeof(unsigned), 1, fileAux) < 1) { //campo1: tamanyo de los registros.
		fclose(fileAux);
		return RES_ERROR;
	}
	if (fwrite(&max_reg, sizeof(unsigned long), 1, fileAux) < 1) { //campo2: cantidad de registros maxima.
		fclose(fileAux);
		return RES_ERROR;
	}

	libre = (char*) malloc(sizeof(char) * max_reg);

	if (!libre) {
			fclose(fileAux);
			return RES_ERROR;
		}

	for (i = 0 ; i < max_reg ; i++)
		libre[i] = REG_LIBRE;


	if (fwrite(libre, sizeof(char) * max_reg, 1, fileAux) < 1) { //campo3: tabla de ocupacion
			free (libre);
			fclose(fileAux);
			return RES_ERROR;
	}
	free(libre);

	unsigned long restante = max_reg * tam_registro;
	unsigned long faltante = 0;

	//Almacena el espacio necesario para los registros
	//Probe empiricamente que los tiempos de escritura disminuyen en GRAN manera
	//si se utiliza la funcion fwrite con el mayor tamanyo de registro posible.
	//Para hallar el tamanyo maximo alocable en memoria con un solo puntero
	//empiezo a aproximar el valor por divisiones (por 2) sucesivas.
	while ( restante > 0 ) {
		if ( !(regAux = malloc(restante)) ) {
			faltante += divisionTecho(restante, 2);
			restante -= divisionTecho(restante, 2);
		}else {
			fwrite(regAux, restante, 1, fileAux);
			free(regAux);
			restante = faltante;
			faltante = 0;
		}
	}

	//Fin: Datos del header del archivo para la organizacion
	fclose(fileAux);

	return RES_OK;
}

fHandler *R_OPEN (const char* nombre_fisico, int modo) {
	FILE *fileAux;
	fHandler *file;

	file = (fHandler*) malloc(sizeof(fHandler));

	if (!file) return NULL;

	file->posicion = -1; //Inicializa el record pointer en un valor aleatorio. R_SEEK es obligatorio
						 //para iniciar la lectura secuencial.


	// Almacena en fHandler (mem):
	// FILE *handler : puntero al archivo stream que se abrio.
	// unsigned tamReg : tamanyo de los registros.
	// unsigned maxReg : cantidad maxima de registros.
	// char *ocupacion : tabla de ocupacion.
	// char modo : modo de apertura READ/WRITE/READ_WRITE.
	if ( (modo == WRITE) || (modo == READ_WRITE) || (modo == READ) ) {
		fileAux = fopen(nombre_fisico, "r+b");
		if (!fileAux) {
			free(file->ocupacion);
			free(file);
			return NULL;
		}
		if (!fread(&(file->tamReg), sizeof(unsigned), 1,fileAux)) {
			free(file->ocupacion);
			free(file);
			fclose(fileAux);
			return NULL;
		}
		if (!fread(&(file->maxReg), sizeof(unsigned long), 1,fileAux)) {
			free(file->ocupacion);
			free(file);
			fclose(fileAux);
			return NULL;
		}
		file->ocupacion = (char*) malloc(sizeof(char) * (file->maxReg));
		if (!(file->ocupacion)) {
			free(file->ocupacion);
			free(file);
			fclose(fileAux);
			return NULL;
		}
		if (!fread(file->ocupacion, sizeof(char) * (file->maxReg), 1, fileAux)) {
			free(file->ocupacion);
			free(file);
			fclose(fileAux);
			return NULL;
		}
		file->handler = fileAux;

		switch(modo) {
			case READ : file->modo = READ; break;
			default : file->modo = WRITE; break;
		}

		return file;
	}

	free(file->ocupacion);
	free(file);
	return NULL;
}

int R_CLOSE (fHandler *handler) {
	if (!handler) return RES_ERROR;
	fseek(handler->handler, sizeof(unsigned) * 2, SEEK_SET);

	//Almacena en el archivo la tabla de ocupacion actualizada antes de cerrar el archivo stream.
	if (fwrite(handler->ocupacion, sizeof(char) * (handler->maxReg), 1, handler->handler) < 1) return RES_ERROR;

	if(fclose(handler->handler)) return RES_ERROR;
	free(handler->ocupacion);
	free(handler);
	return RES_OK;
}

int R_DESTROY(const char* nombre_fisico) {
	if(!remove(nombre_fisico)) return RES_OK;
	return RES_ERROR;
}

int R_READ (fHandler *handler, int nrec, void* reg) {
	if (!handler) return RES_ERROR;
	if(validarPosicion(nrec, handler->maxReg)) return RES_ERROR; //nrec en rango.

	if (handler->ocupacion[nrec] == REG_LIBRE) return RES_NO_EXISTE; //revisa si el registro esta ocupado.

	fseek(handler->handler, posicionar(nrec, handler->maxReg, handler->tamReg), SEEK_SET);

	if (!fread(reg, handler->tamReg, 1, handler->handler)) return RES_ERROR;

	return RES_OK;
}

bool R_EXISTE_REG (fHandler *handler, unsigned long nrec) {
	if (handler->ocupacion[ nrec ] == REG_LIBRE) return false;
	return true;
}

int R_SEEK (fHandler *handler, unsigned long nrec) {
	if (!handler) return RES_ERROR;
	if(validarPosicion(nrec, handler->maxReg)) return RES_ERROR;

	handler->posicion = nrec - 1;

	if (handler->ocupacion[nrec] == REG_LIBRE) return RES_NO_EXISTE;

	return RES_OK;
}

unsigned posicionar(unsigned long nrec, unsigned long maxReg, unsigned tamReg) {
	return 2 * sizeof(unsigned) + sizeof(char) * maxReg + (nrec * tamReg );
}

int validarPosicion(unsigned long nrec, unsigned long maxReg) {
	if ( (nrec < 0) || (nrec > maxReg)) return 1;
	return 0;
}

int R_GETMAXREGS(fHandler *handler) {
	int i;
	int contador = 0;
	for (i = 0 ; i < handler->maxReg ; i++)
		if (handler->ocupacion[i] == REG_OCUPADO)
			contador++;
	return contador;
}

int R_WRITE (fHandler *handler, unsigned long nrec, const void* reg) {
	if (!handler) return RES_ERROR;
	if(validarPosicion(nrec, handler->maxReg)) return RES_ERROR;
	if (handler->modo == READ) return RES_ERROR;
	if (handler->ocupacion[nrec] == REG_OCUPADO) return RES_EXISTE;
	fseek(handler->handler, posicionar(nrec, handler->maxReg, handler->tamReg), SEEK_SET);
	if (fwrite(reg, handler->tamReg, 1, handler->handler) < 1) return RES_ERROR;
	handler->ocupacion[nrec] = REG_OCUPADO;
	return RES_OK;
}

int R_UPDATE (fHandler *handler, unsigned long nrec, const void* reg) {
	if (!handler) return RES_ERROR;
	if(validarPosicion(nrec, handler->maxReg)) return RES_ERROR;
	if (handler->modo == READ) return RES_ERROR;
	if (handler->ocupacion[nrec] == REG_LIBRE) return RES_NO_EXISTE;
	fseek(handler->handler, posicionar(nrec, handler->maxReg, handler->tamReg), SEEK_SET);
	if (fwrite(reg, handler->tamReg, 1, handler->handler) < 1) return RES_ERROR;
	return RES_OK;
}

int R_DELETE (fHandler *handler, unsigned long nrec) {
	if (!handler) return RES_ERROR;
	if(validarPosicion(nrec, handler->maxReg)) return RES_ERROR;
	if (handler->modo == READ) return RES_ERROR;
	if (handler->ocupacion[nrec] == REG_LIBRE) return RES_NO_EXISTE;
	handler->ocupacion[nrec] = REG_LIBRE;
	return RES_OK;
}

int R_READNEXT (fHandler *handler, void* reg) {
	if (!handler) return RES_ERROR;
	int ultimaPosicion = handler->maxReg - 1;
	int error;

	handler->posicion++;

	while ( (handler->ocupacion[handler->posicion] == REG_LIBRE) && (handler->posicion < ultimaPosicion))
		handler->posicion++;

	if (handler->posicion > ultimaPosicion) return RES_EOF;

	error = R_READ(handler, handler->posicion, reg);
	if (error == RES_ERROR) return RES_ERROR;

	return handler->posicion;
}
